#include "maze.h"
#include "box.h"

TileNode::TileNode(int x, int z)
  : x_(x), z_(z)
{
  occupied_ = false;
  initial_ = false;
  tile_type_ = WALL;
  visited_ = false;
  hits_ = kNumHits;
}

Point TileNode::GetIni()
{
  return Point(x_, 0, z_);
}

Point TileNode::GetEnd()
{
  float height = tile_type_ == GROUND ? kGroundHeight : kWallHeight * (0.75 + (0.25 * hits_ / kNumHits));
  return Point(x_ + 1, height, z_ + 1);
}

bool TileNode::IsOccupied()
{
  return occupied_;
}

void TileNode::SetOccupied(bool occupied)
{
  occupied_ = occupied;
}

void TileNode::SetNeighbours(TileNode *n, TileNode *e, TileNode *s, TileNode *w)
{
  neighbours_.push_back(n);
  neighbours_.push_back(e);
  neighbours_.push_back(s);
  neighbours_.push_back(w);
  directions_.push_back(false);
  directions_.push_back(false);
  directions_.push_back(false);
  directions_.push_back(false);
}

void TileNode::SetAsInitial()
{
  initial_ = true;
}

void TileNode::SetType(TileType t)
{
  tile_type_ = t;
  hits_ = kNumHits;
}

void TileNode::SwitchType()
{
  tile_type_ == GROUND ? SetType(WALL) : SetType(GROUND);
}

TileType TileNode::GetType()
{
  return tile_type_;
}

bool TileNode::RandomMove()
{
  if (visited_) {
    return false;
  }
  visited_ = true;
  SetType(GROUND);
  while (!(directions_[NORTH] && directions_[EAST] && directions_[SOUTH] && directions_[WEST])) {
    int r = rand() % DIRS;
    for (int i = 0; i < DIRS; i++)
      if (directions_[r])
        r = (r + 1) % DIRS; // Makes it faster, but slightly skews the algorithm
    if (!directions_[r]) {
      directions_[r] = true;
      if (neighbours_[r] != NULL) {
        neighbours_[r]->GoThrough((Direction)r);
      }
    }
  }
  return true;
}

void TileNode::GoThrough(Direction d)
{
  visited_ = true;
  if (neighbours_[d] != NULL) {
    SetType((neighbours_[d]->RandomMove()) ? GROUND : WALL);
  }
}

void TileNode::SetPassage()
{
  TileType n = neighbours_[NORTH] != NULL ? neighbours_[NORTH]->GetType() : GROUND;
  TileType e = neighbours_[EAST] != NULL ? neighbours_[EAST]->GetType() : GROUND;
  TileType s = neighbours_[SOUTH] != NULL ? neighbours_[SOUTH]->GetType() : GROUND;
  TileType w = neighbours_[WEST] != NULL ? neighbours_[WEST]->GetType() : GROUND;

  // We need to check if two opposite tiles are the same, but different to the consecutives
  if ((n == s) && (e == w) && (n == GROUND || e == GROUND)) {
    SetType(GROUND);
  }
}

void TileNode::Shoot()
{
  hits_--;
  if (hits_ == 0) {
    SetType(GROUND);
  }
}

void TileNode::Render()
{
  float k = 0.5 + (0.5 * hits_ / kNumHits);
  GLfloat red[] = {0.9 * k, 0.2 * k, 0.2 * k, 1.0};
  GLfloat grey[] = {0.6, 0.7, 0.8, 1.0};
  GLfloat green[] = {0.7, 0.9, 0.7, 1.0};
  GLfloat *color = tile_type_ == WALL ? red : initial_? green : grey;

  glColor3f(color[0], color[1], color[2]);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, color);
  glMaterialfv(GL_FRONT, GL_SPECULAR, (GLfloat[]) {0, 0, 0, 1});
  glMaterialfv(GL_FRONT, GL_AMBIENT, (GLfloat[]) {0.1, 0.1, 0.1, 1.0});
  glMaterialfv(GL_FRONT, GL_SHININESS, (GLfloat[]) {0});

  Box::Render(GetIni(), GetEnd());
}

void TileNode::PickRender(GLubyte color[])
{
  glColor3ub(color[0], color[1], color[2]);

  Box::Render(GetIni(), GetEnd());
}

// Maze ///////////////////////////////////////////////////////////////////////////

Maze::Maze(int length, int width)
  :length_(length), width_(width)
{
  // Set seed, so that we always obtain the same maze for a same size
  srand(3210);

  // Maze has to be at least size 10x10
  if (length < 10) {
    length_ = length = 10;
  }
  if (width < 10) {
    width_ = width = 10;
  }

  // Boundary
  ini_ = Point(0, 0, 0);
  end_ = Point(length, kWallHeight, width);

  // Create empty Maze
  grid_ = std::vector< std::vector< TileNode* > >(length, std::vector< TileNode* >(width));
  for (int i = 0; i < length; i++) {
    for (int j = 0; j < width; j++) {
      grid_[i][j] = new TileNode(i, j);
    }
  }
  // Set neighbours
  for (int i = 0; i < length; i++) {
    for (int j = 0; j < width; j++) {
      TileNode* n = i == length - 1 ? NULL : grid_[i+1][j];
      TileNode* e = j == width - 1 ? NULL : grid_[i][j+1];
      TileNode* s = i == 0 ? NULL : grid_[i-1][j];
      TileNode* w = j == 0 ? NULL : grid_[i][j-1];
      grid_[i][j]->SetNeighbours(n, e, s, w);
    }
  }
  // Generate random Maze recursively
  grid_[length_/2][width_/2]->SetAsInitial();
  grid_[length_/2][width_/2]->RandomMove();

  // Add some more ground tiles randomly
  for (int n = 0; n < length * width * kChange; n++) {
    grid_[rand()%length][rand()%width]->SetPassage();
  }
}

Maze::~Maze()
{
  for (int i = 0; i < length_; i++) {
    for  (int j = 0; j < width_; j++) {
      delete grid_[i][j];
    }
  }
}

bool Maze::IsValid(int x, int z)
{
  return x >= 0 && x < length_ && z >= 0 && z < width_;
}

bool Maze::IsGround(int x, int z)
{
  if (IsValid(x, z)) {
    return grid_[x][z]->GetType() == GROUND;
  }
  return false;
}

bool Maze::IsOccupied(int x, int z)
{
  if (IsValid(x, z)) {
    return grid_[x][z]->IsOccupied();
  }
  return false;
}

bool Maze::SetOccupied(int x, int z, bool occupy)
{
  if (IsValid(x, z) && IsGround(x, z)) {
    grid_[x][z]->SetOccupied(occupy);
    return true;
  }
  return false;
}

void Maze::Switch(int i, int j)
{
  if (IsValid(i, j) && !IsOccupied(i, j)) {
    grid_[i][j]->SwitchType();
  }
}

void Maze::Shoot(int i, int j) {
  if (IsValid(i, j)) {
    grid_[i][j]->Shoot();
  }
}

Point Maze::GetBoundaryIni()
{
  return ini_;
}

Point Maze::GetBoundaryEnd()
{
  return end_;
}

Point Maze::GetCenter()
{
  return Point((ini_.x + end_.x) / 2.0, (ini_.y + end_.y) / 2.0, (ini_.z + end_.z) / 2.0);
}

float Maze::GetRadius()
{
  Point c = GetCenter();
  return sqrt(
        (c.x - ini_.x)*(c.x - ini_.x) +
        (c.y - ini_.y)*(c.y - ini_.y) +
        (c.z - ini_.z)*(c.z - ini_.z));
}

void Maze::Render()
{
  for (int i = 0; i < length_; i++) {
    for  (int j = 0; j < width_; j++) {
      grid_[i][j]->Render();
    }
  }
}

void Maze::PickRender()
{
  for (GLubyte i = 0; i < length_; i++) {
    for (GLubyte j = 0; j < width_; j++) {
      GLubyte color[3] = {i, j, 0};
      grid_[i][j]->PickRender(color);
    }
  }
}
